import java.io.*;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * <p>Title: Request.java</p>
 *
 * <p>Description: Used to store and process requests that come from the client
 * to the server. </p>
 *
 * <p>Copyright: Copyright (c) 2011</p>
 *
 * <p>Company: </p>
 * @author David Beaver and Aishwarya Iyer
 * @version 1.0
 */

public class Request{
    private String method;
    private String URI;
    private String query;
    private String version;
    private String body;
    private String mediaType;
    private Vector<String> headers; //request header
    private HashMap<String,String> headerTags; //mapping of headers to their tags
    private HttpdConf serverConfiguration;
    private String cacheDate;
    /**
    * Default constructor used to reset your variables and data structures for
    * each new incoming request.
    */

    public Request() {
        this.method = "";
        this.URI = "";
        this.mediaType = "";
        this.query = "";
        this.version = "";
        this.body = "";
        this.headers = new Vector<String>();
        this.headerTags = new HashMap<String,String>();
        this.cacheDate = "";
    }

    public Request(HttpdConf serverConfig){
        this.method = "";
        this.URI = "";
        this.mediaType = "";
        this.query = "";
        this.version = "";
        this.body = "";
        this.headers = new Vector<String>();
        this.headerTags = new HashMap<String,String>();
        this.serverConfiguration = serverConfig;
        this.cacheDate = "";
    }
    
    public String getCGI(){
        return this.serverConfiguration.solveAlias(URI);
    }

    public String getMediaType(){
        return this.mediaType;
    }
    public String getMethod(){
        return this.method;
    }

    public String getURI(){
        return this.URI;
    }

    public String getVersion(){
        return this.version;
    }

    public String getQuery(){
        return this.query;
    }

    public boolean keepAlive(){
        return this.headerTags.containsKey("Keep-Alive:");
    }

    public String getAuthentication(){
        if(this.headerTags.containsKey("Authorization:")){
            return this.headerTags.get("Authorization:");
        }
        return "";
    }
    //Cache-Control: public or must-revalidate
    public Date getCacheDate(){
        if(this.headerTags.containsKey("If-Modified-Since:")){
            String date = this.headerTags.get("If-Modified-Since:");
            //Date tst = new Date();
            //dow mon dd hh:mm:ss zzz yyyy
            DateFormat datef = new SimpleDateFormat("EEE MMM d kk:mm:ss z yyyyy");
            try {
                return datef.parse(date);
            } catch (ParseException ex) {
                Logger.getLogger(Request.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return null;
    }
    /**
    * Parse the incoming request depending on the type of request you are
    * receiving. This information is found from the first line of the incoming
    * request. You will also want to check and make sure the request you are
    * receiving is a valid request. If the request is not valid, throw an error
    * using the http error codes.
    *
    * @param inMsg BufferedReader which grabs the incoming message from the
    *   client socket
    */
    public void parse(BufferedReader inMsg) {
        try {
            if (inMsg.ready()) {
                //parse the first line and extract parameters if GET or HEAD
                this.parseFirstLine(inMsg.readLine());
                String nextLine = "";
                //the headers need to be extracted
                StringTokenizer parser = new StringTokenizer(inMsg.readLine()); //read the next line into the parser
                while(parser.hasMoreTokens()){ //while the parser has found a header
                    this.headers.add(parser.nextToken());  //save the header
                    if(parser.hasMoreTokens()){ //if the header has some tags
                        this.headerTags.put(this.headers.lastElement(), (parser.nextToken("\n")).trim()); //save it's tags
                    }
                    nextLine = inMsg.readLine(); //read the next line's header
                    if(nextLine != null) //if there is a header
                        parser = new StringTokenizer(nextLine); //read the header into the parser
                    //the line is blank but there could be a body
                    else if(this.method.equals("POST") || this.method.equals("PUT")){ //if the method is PUT or POST
                        //there exists a body to parse
                        parser = new StringTokenizer(inMsg.readLine()); //read the body into the parser
                        if(parser.hasMoreTokens()){ //if the parser finds a query
                            this.query = parser.nextToken("\n"); //save the query
                        }
                    }else{ //otherwise
                        parser = new StringTokenizer(""); //finish reading response
                    }
                }
                //this.ifTag = this.headerTags.get("If-Range:");
                this.print();
            }
        } catch (IOException ex) {
            this.serverConfiguration.writeLogFile("**Error**: An error occured while retrieving the the http request\n");
            System.out.println("An error occured while retrieving http request");
        }
    }

    /**
    * Used to first check whether a requested file path has an alias set within
    * the configuration file and if so, replaces the alias of the file or
    * directory with the real path. This way, the server can find the right file
    * in the tree.
    *
    * HINT: Remember that any one path can have multiple aliases found within the
    * httpd.conf file. For example, the URI
    * http://www.blah.net/blah/help/hello.html could have an alias where blah is
    * equivalent to http://www.blah.net/blah_blah_blah and help could be an alias
    * for http://www.blah.net/blah_blah_blah/bleh/help. Another thing to note is
    * that your URI could also include script aliases which means you may be
    * required to run a cgi script.
    *
    * @param config HttpdConf Object which contains all the information on how
    *   the server is configured.
    */
    public void setHttpdConf(HttpdConf config) {
        if(config.isScript(this.URI)){
            this.URI = config.solveAlias(this.URI);
        }
    }
  
    /**
    * Print function used for debugging purposes. Helpful to make sure you are
    * parsing the incoming request properly.
    */
    public void print() {
        this.serverConfiguration.writeLogFile("REQUEST:\n");
        this.serverConfiguration.writeLogFile(method + " " + URI.trim() + " " + version + "\n");
        System.out.println("The method was " + method);
        System.out.println("The Request URL was " + URI);
        System.out.println("The query string was " + query);
        System.out.println("The HTTP version is " + version);
        System.out.println("The following headers were included:");
        for(String header: this.headers){
            System.out.println(header + this.headerTags.get(header));
            this.serverConfiguration.writeLogFile(header + this.headerTags.get(header));
             this.serverConfiguration.writeLogFile("\n");
        }
        if(this.body.length() > 0){
            System.out.println("\nThe body:");
            System.out.println(this.body);
        }
        this.serverConfiguration.writeLogFile("\n");
    }

    /**
    * private function used by request object to parse the information passed
    * through from the client to the server and save it for future use. The type
    * of request can be found on this first line of the request.
    *
    * @param first String representation of the first line of the request from
    *   the client. Passed in as one long string which can easily be parsed.
    */
    private void parseFirstLine(String first) {
        StringTokenizer parser = new StringTokenizer(first); //read the first line into the parser
        StringTokenizer parameterParser = new StringTokenizer(first);
        this.method = parser.nextToken(); //get the method
        if(this.checkRequest()){ //check if the method is a valid method
            if(this.method.equals("GET")||this.method.equals("HEAD")){ //if the method is get or head
                //argument are on this same line
                parameterParser.nextToken("?"); //parse until a query is found
                if(parameterParser.countTokens() > 0){ //if a query is found
                    this.URI = parameterParser.nextToken(); //save the URI
                    this.query = parameterParser.nextToken(" "); //get the query
                    StringTokenizer extensionParser = new StringTokenizer(this.URI,"."); //parse the line for the extension
                    this.mediaType = extensionParser.nextToken(); //save the extension
                }else{ //no query is present
                    this.URI = parser.nextToken(" "); //save the URI
                    StringTokenizer extensionParser = new StringTokenizer(this.URI,"."); //parse the line for the extension
                    if(extensionParser.countTokens() < 2 || (this.URI.length() == 1)){ //if no extension was found
                        this.mediaType = "text/html"; //assume the media type will be text/html
                    }
                    extensionParser.nextToken();
                    this.mediaType =  extensionParser.nextToken(); //save the extension
                    this.mediaType = this.serverConfiguration.getMediaType(mediaType);
                }
                this.version = parser.nextToken("\n"); //get the version
            }
        }else{
            //invalid request
            //throw error
        }
    }

    /**
    * private function used by the request object to determine whether an incoming
    * request is a valid request or not. Useful when throwing error messages.
    *
    * @return true if request is valid, false otherwise
    */
    private boolean checkRequest() {
        if(this.method.equals("GET")||this.method.equals("HEAD")||this.method.equals("POST")||this.method.equals("PUT")){
             return true;
        }
        return false;
    }

    /**
    * private function used by the request object to grab variables that may have
    * been passed to the server when the request was made. Remember that GET and
    * HEAD requests include their variables on the first line of the request while
    * POST and PUT requests include their variables within the body of the
    * message.
    */
    private void setVarFirstLine() {}

    /**
    * private function used by the request object to grab variables that may have
    * been passed to the server when the request was made. Remember that POST and
    * PUT requests include their variables within the body of the message and not
    * in the first line, so another method is needed to retrieve these variables.
    */
    private void setVarNotFirstLine() {}

    /**
    * private function used by the request object to parse the rest of the request
    * message (e.g. other headers and the body of the message) from the client so
    * it can be used later when actual processing of the request happens.
    *
    * @param inFile BufferedReader object that comes through the socket. Needs to
    *   be processed properly before the data stored within it can be used.
    */
    private void createRequest(BufferedReader inFile) {}
}
